package com.csci4448.distributedcomputing;

import akka.actor.Actor
import akka.actor.Actor._

import java.net.URLClassLoader
import java.net.URL

import edu.colorado.cs._

class Client(val host: String) {
  def start_computing() = {
    // Get a handle to the remote actor. In the Android client, the host
    // (second parameter) and port (third parameter) will be determined
    // based on user input.
    // TODO: What happens if the server can't be reached?
    val actor = Actor.remote.actorFor("actor", host, 1337)

    // This URL is for the URLClassLoader. It should be:
    // http://<host>:<port+1>/    (The ending slash is important!)
    //
    // The port for this URL is one more than the port defined by the user
    // because the URL is actually pointing to a different server that
    // serves class files.
    val urls = Array(new URL(host + ":1338/"))

    // This gets a new URLClassLoader that looks in the previously defined
    // URL for classes and with the default class loader as its parent.
    val cl = new URLClassLoader(urls, getClass().getClassLoader())

    // This initializes the Class object that represents the ProjectInfo
    // class we want an instance of. It's initialize to None because we
    // don't have it yet.
    var pi_class: Option[Class[ProjectInfo]] = None

    // Here we're actually loading the class via the URLClassLoader. If
    // there is an issue, a ClassNotFoundException is thrown. Scala handles
    // exceptions a little differently from Java.
    try {
      pi_class = Some(cl.loadClass("MyProjectInfo").asInstanceOf[Class[ProjectInfo]])
    } catch {
      case e: ClassNotFoundException => {
        // This means that the server is most likely down.
        // TODO: What happens for Android?
      }
    }

    // Here we're actually getting an instance of the ProjectInfo class
    // that we got from the server. Remember, this defines the name and
    // description of the project, and the function that the clients need
    // to exectute.
    val project_info: Option[ProjectInfo] = pi_class match {
      case Some(c: Class[ProjectInfo]) => Some(c.newInstance())
      case None => None
    }

    // This variable determines whether the client should continue
    // looking for data. This mechanism will be more complicated in the
    // Android client.
    var continue = true

    while (continue) {
      // The !! function sends a message to an actor and waits for a
      // response. This response needs to be pattern-matched in order to
      // determine what it was we got back.
      actor !! GetData() match {
        case Some(message: Data) => message match {
          // At this point we know we've got a Data message.
          // (see Messages.scala)
          case Data(value: Double) => {
            // In order to use the instance of ProjectInfo, we need to make
            // sure that we actually have an instance of ProjectInfo.
            project_info match {
              // Here's where the magic happens. If we've got some data and
              // we have an instance of ProjectInfo, then we send the server
              // a Result message that contains the input value and the
              // output as determined by the Function1 object stored in the
              // instance of ProjectInfo.
              //
              // Although p.func is actually a variable of type Function1,
              // it has a method called "apply" defined. That allows you to
              // "use" it as if it were a function.
              //
              // The ! function (we used !! before) sends a message and
              // does not wait for a response. It is asynchronous.
              case Some(p: ProjectInfo) => actor ! Result(value, p.func(value))
              case None =>
            }
          }
          // I don't know whether this case is required. The underscore acts
          // as a wildcard in Scala. (In this case, anyway.) Think of this as
          // "default" in a switch statement. I don't know if getting here
          // is even possible.
          case _ =>
        }
        case Some(message: NoData) => {
          // In this case, we've received a NoData message. That indicates
          // that the server is out of data to send. (see Messages.scala)
          continue = false
        }
        // In this case, the server has failed to send a message to the
        // client.
        case None =>
      }
    }
  }
}
